package namespaces

import (
	"errors"

	"gddgame.cc/galaxy/asteroid"
	"gddgame.cc/galaxy/asteroid/cluster"
	"gddgame.cc/galaxy/binocle"
	"gddgame.cc/galaxy/common/bootstrap"
	"gddgame.cc/galaxy/common/service/lobby"
	"gddgame.cc/galaxy/common/service/rank"
	"gddgame.cc/galaxy/core/sdk"
	"gddgame.cc/galaxy/satellite/formula/nebula"
	"gddgame.cc/galaxy/server/composer"
	"gddgame.cc/galaxy/utils"
	"gddgame.cc/galaxy/utils/def"
)

//
const (
	LobbyJoin nebula.NSE = iota
	LobbyLeave
	TeamCreate
	LeaderboardUpdate
	LeaderboardNumber

	OnEventNotice nebula.NSE = 125
	OnRoomConnect nebula.NSE = 126
	OnTeamInvite  nebula.NSE = 127
)

type SceneManager interface {
	UpdateLeaderboard(region string, openId string, identity string, value int) error
	NumberLeaderboard(region string, openId string, identity string) (int, error)
	ListLeaderboard(region string, identity string, number int) ([]rank.Position, error)
	Depends() error
}

type SceneDelegate interface {
	GetRoom(openId string) string
	GetTeam(openId string) string
	LeaveRoom(token string, openId string)
	LeaveTeam(token string, openId string)
}

type sceneService struct {
	builder composer.Builder
	binocle.App
	Sdk      *sdk.AppClient
	Delegate SceneDelegate
	Rank     *rank.Manager

	Room RoomManager
	Team TeamManager
}

func (service *sceneService) UpdateLeaderboard(region string, openId string, identity string, value int) error {
	leaderboard, err := service.Sdk.GetLeaderboard(identity)
	if err != nil {
		return err
	}
	if _, err := service.Sdk.UpdateLeaderboard(openId, leaderboard, value); err != nil {
		return err
	}

	instance := service.Rank.GetInstance(region, utils.ToString(leaderboard.Id), leaderboard.Asc)
	return instance.Update(openId, value)
}

func (service *sceneService) NumberLeaderboard(region string, openId string, identity string) (int, error) {
	leaderboard, err := service.Sdk.GetLeaderboard(identity)
	if err != nil {
		return 0, err
	}
	instance := service.Rank.GetInstance(region, utils.ToString(leaderboard.Id), leaderboard.Asc)
	c := instance.Rank(openId)
	return c, nil
}

func (service *sceneService) ListLeaderboard(region string, identity string, number int) ([]rank.Position, error) {
	leaderboard, err := service.Sdk.GetLeaderboard(identity)
	if err != nil {
		return nil, err
	}
	instance := service.Rank.GetInstance(region, utils.ToString(leaderboard.Id), leaderboard.Asc)
	result := instance.Top(number)
	return result, nil
}

func (service *sceneService) Depends() error {
	service.builder.Factory("room", &service.Room)
	if service.Room == nil {
		return errors.New("Scene need Room")
	}
	service.builder.Factory("team", &service.Team)
	if service.Team == nil {
		return errors.New("Scene need Team")
	}
	return nil
}

/*
 场景逻辑
*/
func SceneHandler(builder composer.Builder, namespace nebula.Namespace, client *sdk.AppClient, delegate SceneDelegate) SceneManager {
	var agent binocle.App
	builder.Factory("agent", &agent)
	var boot *bootstrap.Boot
	builder.Factory("boot", &boot)

	// 匹配服务: 业务自定义类型
	lobbyService := lobby.Default()
	rankService := rank.Default()
	rankService.RegisterRedis(boot.Redis())

	sceneManager := &sceneService{builder: builder, App: agent}
	_ = builder.BindInstance("scene", sceneManager)

	// 绑定系统通道
	sceneManager.Sdk = client
	sceneManager.Delegate = delegate
	sceneManager.Rank = rankService

	lobbyService.OnSuccess(func(container cluster.Container, users map[string]string, matchId string, option map[string]interface{}) {
		builder.Debugf("Lobby Success:%#v, %s:%s", users, matchId, option)
		_ = sceneManager.Room.Create(container, users, option)
	})
	agent.Cluster().RegisterHash(lobbyService)

	_ = namespace.Connect(func(socket *nebula.Socket, next nebula.Next) {
		var openId string
		if err := socket.Get("open_id", &openId); err != nil {
			_ = next(errors.New("Auth error"))
			return
		}
		_ = next(nil)

		single := agent.GetUserRoom(socket, openId)
		single.Filter("room/connect", func(message asteroid.Message, member def.Member) {
			//ss := member.(*nebula.Socket)
			token := message.Data().(string)
			number := message.Read(1)
			builder.Debugf("room/connect: %s, %d, %#v", token, number, socket.ExitStatus)
			if socket.ExitStatus != nebula.NoneExit {
				return
			}
			_ = socket.Clean("scene/lobby")
			roomToken := delegate.GetRoom(openId)
			if roomToken == "" {
				_ = socket.Emit(OnRoomConnect, token, utils.ToInt(number))
			} else {
				builder.Debugf("room/connected: %s", roomToken)
			}
		})
		single.Filter("team/invite", func(message asteroid.Message, member def.Member) {
			from := message.Read(0).(string)
			token := message.Read(1).(string)
			builder.Debugf("team/invite:%s, %s, %#v", from, token, socket.ExitStatus)
			if socket.ExitStatus != nebula.NoneExit {
				return
			}
			teamToken := delegate.GetTeam(openId)
			if teamToken == "" {
				_ = socket.Emit(OnTeamInvite, from, token)
			} else {
				builder.Debugf("team/invited: %s", teamToken)
			}
		})
		single.Filter("event/notice", func(message asteroid.Message, member def.Member) {
			if socket.ExitStatus != nebula.NoneExit {
				return
			}
			_ = socket.Emit(OnEventNotice, message.List()...)
		})
		roomToken := delegate.GetRoom(openId)
		if roomToken != "" {
			if sceneManager.Room.Exist(agent, roomToken) {
				_ = socket.Emit(OnRoomConnect, roomToken, 0)
			} else {
				delegate.LeaveRoom(roomToken, openId)
			}
		}
		teamToken := delegate.GetTeam(openId)
		if teamToken != "" {
			if sceneManager.Team.Exist(agent, teamToken) {
				_ = socket.Emit(OnTeamInvite, "", teamToken)
			} else {
				delegate.LeaveTeam(teamToken, openId)
			}
		}
		// 从message中获取
		//single.Filter("friend/invite", func(message *asteroid.Message, member def.Member) {
		//	from := message.Read(0).(string)
		//	builder.Debugf("team/invite:%#v", from)
		//	_ = socket.Emit(OnFriendInvite, from)
		//})
	})
	_ = namespace.Disconnect(func(socket *nebula.Socket, reason string) {
		var openId string
		if err := socket.Get("open_id", &openId); err != nil {
			return
		}
		var channel string
		if err := socket.Get("scene/lobby", &channel); err == nil {
			lobbyService.Leave(agent.Hash(), channel, openId, false)
			_ = socket.Set("scene/lobby", nil)
		}
	})

	// 进入等待队列
	_ = namespace.On(LobbyJoin, func(req *nebula.Request, lobbyName string, options map[string]interface{}) {
		m := lobbyService.Get(lobbyName)
		if m == nil {
			req.Response(false)
			return
		}
		var openId string
		if err := req.Get("open_id", &openId); err != nil {
			req.Response(false)
			return
		}
		members := map[string]string{openId: ""}
		if m.Need(members, options) {
			channel, payload, err := m.Channel(members, options)
			if err != nil {
				req.Response(false)
				return
			}
			l, err := lobbyService.Join(agent.Hash(), lobbyName, members, channel, payload)
			if err != nil {
				req.Response(false)
				return
			}
			_ = req.Set("scene/lobby", l)
		} else {
			_ = sceneManager.Room.Create(agent, members, options)
		}
		req.Response(true)
	})
	_ = namespace.On(LobbyLeave, func(req *nebula.Request) {
		var openId string
		if err := req.Get("open_id", &openId); err != nil {
			return
		}
		var channel string
		if err := req.Get("scene/lobby", &channel); err == nil {
			lobbyService.Leave(agent.Hash(), channel, openId, false)
			req.Response(true)
		} else {
			req.Response(false)
		}
	})
	_ = namespace.On(TeamCreate, func(req *nebula.Request, people int) {
		team := sceneManager.Team.Create(agent, people)
		req.Response(team)
	})
	_ = namespace.On(LeaderboardUpdate, func(req *nebula.Request, identity string, value int) {
		var openId string
		if err := req.Get("open_id", &openId); err != nil {
			req.SendError(err)
			req.Response(false)
			return
		}
		if err := sceneManager.UpdateLeaderboard(agent.Name(), openId, identity, value); err != nil {
			req.SendError(err)
			req.Response(false)
			return
		}
		req.Response(true)
	})
	_ = namespace.On(LeaderboardNumber, func(req *nebula.Request, identity string) {
		var openId string
		if err := req.Get("open_id", &openId); err != nil {
			req.SendError(err)
			req.Response(0)
			return
		}
		if c, err := sceneManager.NumberLeaderboard(agent.Name(), openId, identity); err != nil {
			req.SendError(err)
			req.Response(0)
			return
		} else {
			req.Response(c)
		}
	})
	return sceneManager
}
